package com.yinshu.teleshield.tkbase.controller;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.yinshu.teleshield.tkbase.base.R;
import com.yinshu.teleshield.tkbase.base.entity.TKSuperEntity;
import com.yinshu.teleshield.tkbase.cache.SovitsCacheKey;
import com.yinshu.teleshield.tkbase.controller.helper.PlusSheetWriteHandler;
import com.yinshu.teleshield.tkbase.redis.DistributedLock;
import com.yinshu.teleshield.tkbase.request.PageParams;
import com.yinshu.teleshield.tkbase.service.SuperService;
import com.yinshu.teleshield.tkbase.utils.ExcelEasyUtils;
import com.yinshu.teleshield.tkbase.utils.verify.Verify;
import com.yinshu.teleshield.utils.CommonBeanUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


/**
 * 导入导出
 *
 * @author yangjunxiong
 */
@Slf4j
public abstract class SuperExcelController<S extends SuperService<Id, Entity>,
        Id extends Serializable, Entity extends TKSuperEntity<Id>, SaveVO, UpdateVO, PageQuery, ResultVO, ExcelVO extends Verify>
        extends SuperController<S, Id, Entity, SaveVO, UpdateVO, PageQuery, ResultVO> {
    @Autowired
    private DistributedLock redisLock;

    private static final String FILE_NAME = "filename";

    private static final String TYPE = "type";

    protected Class<ExcelVO> execlVOClass = currentExeclVOClass();

    protected Class<ExcelVO> currentExeclVOClass() {
        return (Class<ExcelVO>) ReflectionKit.getSuperClassGenericType(this.getClass(), SuperExcelController.class, 7);
    }

    public Class<ExcelVO> getExeclVOClass() {
        return this.execlVOClass;
    }

    //导入 导出 基础配置
    public abstract Pair<AnalysisEventListener<ExcelVO>, PlusSheetWriteHandler> execlHandler(Integer lastRow);

    //重写可自定义复杂表头
    public List<List<String>> execlHand() {
        return List.of();
    }

    /**
     * 导出Excel
     *
     * @param params   参数
     * @param request  请求
     * @param response 响应
     */
    @Operation(summary = "导出Excel", description = "导出Excel")
//    @OperateLog(name = "导出Excel", type = {OperateTypeEnum.GET})
    @PostMapping(value = "/export", produces = "application/octet-stream")
    public void exportExcel(@RequestBody @Validated PageParams<PageQuery> params, HttpServletRequest request, HttpServletResponse response) throws IOException {
        String start = DateUtil.now();
        log.info("{}.exportExcel.导出Excel开始:{},请求参数:params:{}", this.getClass().getName(), start, CommonBeanUtils.toJSONString(params));
        String type = (String) params.getExtra().getOrDefault(TYPE, "数据");
        String filename = (String) params.getExtra().getOrDefault(FILE_NAME, "导出");
        List<?> list = new ArrayList<>();
        if (StrUtil.equals(type, "数据")) {
            list = findExportList(params);
        }
        ExcelEasyUtils.exportExcel(response, filename, getExeclVOClass(), list, execlHand(), execlHandler(list.size()).getRight());
        String end = DateUtil.now();
        log.info("{}.exportExcel.导出Excel结束:{},耗时:{},导出ExeclVO:{}", this.getClass().getName(), end, DateUtil.formatBetween(DateUtil.parse(start), DateUtil.parse(end)), getExeclVOClass().getSimpleName());
    }

    /**
     * 查询待导出的数据， 子类可以重写
     *
     * @param params params
     * @return java.util.List<?>
     * @author yangjunxiong
     */
    public List<?> findExportList(PageParams<PageQuery> params) {
        params.setPageSize(params.getPageSize() == -1 ? Convert.toLong(Integer.MAX_VALUE) : params.getPageSize());
        IPage<ResultVO> page = query(params);
        if (CollUtil.isEmpty(page.getRecords())) {
            return Collections.emptyList();
        }
        return page.getRecords();
    }

    @Operation(summary = "导入Excel", description = "导入Excel")
    @PostMapping(value = "/import")
    public R<Boolean> importExcel(@RequestParam(value = "file") MultipartFile simpleFile,
                                  HttpServletRequest request, HttpServletResponse response) throws Exception {
        String start = DateUtil.now();
        log.info("{}.importExcel.导入Excel开始{},导入ExeclVO:{}", this.getClass().getName(), start, getExeclVOClass().getSimpleName());
        // 配置excel第一行字段名
        String key = SovitsCacheKey.EXECL + getExeclVOClass().getSimpleName();
        try {
            if (redisLock.lock(key, 1000 * 60L * 2, 1)) {
                Pair<AnalysisEventListener<ExcelVO>, PlusSheetWriteHandler> pair = execlHandler(1);
                List<ExcelVO> dataList = EasyExcel
                        .read(simpleFile.getInputStream())
                        .head(execlHand())
                        .head(getExeclVOClass())
                        .registerReadListener(pair.getLeft())
                        .sheet()
                        .doReadSync();
                List<Entity> list = validData(dataList);
                superService.saveBatch(list);
                return R.success();
            } else {
                return R.fail("服务器繁忙，请稍后再试。");
            }
        } catch (ExcelDataConvertException e) {
            log.error("导入数据格式错误", e);
            ExcelContentProperty excelContentProperty = e.getExcelContentProperty();
            Field field = excelContentProperty.getField();
            ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
            String name = "";
            if (excelProperty != null) {
                name = StrUtil.join(".", excelProperty.value());
            } else {
                Schema apiModelProperty = field.getAnnotation(Schema.class);
                if (apiModelProperty != null) {
                    name = apiModelProperty.description();
                } else {
                    name = field.getName();
                }
            }
            Integer rowIndex = e.getRowIndex();
            Integer columnIndex = e.getColumnIndex() + 1;
            String value = e.getCellData().getStringValue();
            String msg = "第{}行，第{}列，字段【{}】的参数值：【{}】 填写有误，请认真检查。";
            return R.fail(StrUtil.format(msg, rowIndex, columnIndex, name, value));
        } finally {
            redisLock.releaseLock(key);
            String end = DateUtil.now();
            log.info("{}.importExcel.导入Excel结束{},耗时:{},导入ExeclVO:{}", this.getClass().getName(), end, DateUtil.formatBetween(DateUtil.parse(start), DateUtil.parse(end)), getExeclVOClass().getSimpleName());
        }
    }

    /**
     * 子类实现业务校验规则，校验失败自行报错
     * @param dataList 读取的原始数据
     */
    protected List<Entity> validData(List<ExcelVO> dataList) {
        // 业务校验

        // 数据转换
        return BeanUtil.copyToList(dataList, getEntityClass());
    }
}
